﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Peer
{
    public class PeerClient : PeerStream, IDisposable
    {
        public event EventHandler<PeerStream> OnConnected;

        private readonly EndPoint _endPoint;

        private readonly byte[] cipher;
        private readonly bool isUdp;
        private readonly AddressFamily family;

        /// <summary>
        /// 服务器时差，丢包算法
        /// </summary>
        private int timeDifference;

        public bool IsConnected { get; set; }
        protected override bool CanSend => IsConnected;

        public PeerClient(string cipher, EndPoint targetPoint) : this(cipher, targetPoint, PeerOptions.Default)
        {

        }

        public PeerClient(string cipher, EndPoint targetPoint, PeerOptions options) : base(Guid.NewGuid(), null, options.PeerConverter)
        {
            this.cipher = Encoding.ASCII.GetBytes(cipher);
            this.isUdp = false;
            this.family = options.AddressFamily;
            _endPoint = targetPoint;
        }



        public void Connect()
        {
            if (Socket != null && Socket.Connected)
            {
                Socket.Close();
            }
            IsConnected = true;
            Socket = new Socket(family, isUdp ? SocketType.Dgram : SocketType.Stream, isUdp ? ProtocolType.Udp : ProtocolType.Tcp);
            Socket.Connect(_endPoint);
            VerifyHandshake();
            Task.Factory.StartNew(ReceiveAsync, TaskCreationOptions.LongRunning)
                .ContinueWith(e =>
                {
                    Close();
                });
            OnConnected?.Invoke(this, this);
        }

        private void VerifyHandshake()
        {
            Socket.Send(Handshake.NewHandshake(cipher).Bytes);
            Socket.ReceiveTimeout = 2000;
            var headers = new byte[Handshake.HANDSHAKE_LENGTH];
            var length = Socket.Receive(headers);
            var handshake = Handshake.Parse(headers, length);
            timeDifference = (int)(handshake.Timestamp - DateTimeOffset.Now.ToUnixTimeSeconds());
        }

        public PeerClient WithAutomaticReconnect(RetryPolicy retryPolicy)
        {
            OnDisconnect += (sender, e) =>
            {
                IsConnected = false;
                if (cancellationTokenSource.IsCancellationRequested)
                {
                    return;
                }
                int i = 0;
                while (true)
                {
                    var timeSpane = retryPolicy.Invoke(i++);
                    try
                    {
                        Thread.Sleep(timeSpane);
                        Connect();
                        break;
                    }
                    catch (Exception ex)
                    {

                    }
                }
            };
            return this;
        }

        public override void Dispose()
        {
            cancellationTokenSource.Cancel();
            Close(false);
            GC.SuppressFinalize(this);
        }
    }
}
